<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>The source code</title>
  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  <style type="text/css">
    .highlight { display: block; background-color: #ddd; }
  </style>
  <script type="text/javascript">
    function highlight() {
      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
    }
  </script>
</head>
<body onload="prettyPrint(); highlight();">
  <pre class="prettyprint lang-js">var dataUtil = require(&quot;../utils/data_structure_util&quot;);

var classUtil = require(&quot;../utils/class_util&quot;);

var Eventful = require(&quot;../event/Eventful&quot;);

var Transformable = require(&quot;./transform/Transformable&quot;);

var Control = require(&quot;./transform/TransformControl&quot;);

var Animatable = require(&quot;../animation/Animatable&quot;);

var Style = require(&quot;./Style&quot;);

var RectText = require(&quot;./RectText&quot;);

var guid = require(&quot;../utils/guid&quot;);

var Draggable = require(&quot;./drag/Draggable&quot;);

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(&quot;Cannot call a class as a function&quot;); } }

function _defineProperties(target, props) { for (var i = 0; i &lt; props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (&quot;value&quot; in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

<span id='qrenderer-graphic-Element'>/**
</span> * @class qrenderer.graphic.Element
 * 
 * Root class, everything in QuarkRenderer is an Element. 
 * This is an abstract class, please don&#39;t creat an instance directly.
 * 
 * 根类，QRenderer 中所有对象都是 Element 的子类。这是一个抽象类，请不要直接创建这个类的实例。
 * 
 * @docauthor 大漠穷秋 &lt;damoqiongqiu@126.com&gt;
 */
var Element =
/*#__PURE__*/
function () {
<span id='qrenderer-graphic-Element-method-constructor'>  /**
</span>   * @method constructor Element
   */
  function Element() {
    var _this = this;

    var options = arguments.length &gt; 0 &amp;&amp; arguments[0] !== undefined ? arguments[0] : {};

    _classCallCheck(this, Element);

<span id='qrenderer-graphic-Element-property-options'>    /**
</span>     * @protected
     * @property options 配置项
     */
    this.options = options;
<span id='qrenderer-graphic-Element-property-id'>    /**
</span>     * @property {String} id
     */

    this.id = &#39;el-&#39; + guid();
<span id='qrenderer-graphic-Element-property-name'>    /**
</span>     * @property {String} name 元素名字
     */

    this.name = &#39;&#39;;
<span id='qrenderer-graphic-Element-property-type'>    /**
</span>     * @property {String} type 元素类型
     */

    this.type = &#39;element&#39;;
<span id='qrenderer-graphic-Element-property-parent'>    /**
</span>     * @property {Element} parent 父节点，添加到 Group 的元素存在父节点。
     */

    this.parent = null;
<span id='qrenderer-graphic-Element-property-ignore'>    /**
</span>     * @property {Boolean} ignore
     * 
     * Whether ignore drawing and events of this object.
     * 
     * 为 true 时忽略图形的绘制以及事件触发
     */

    this.ignore = false;
<span id='qrenderer-graphic-Element-property-clipPath'>    /**
</span>     * @property {Path} clipPath
     * 
     * This is used for clipping path, all the paths inside Group will be clipped by this path, 
     * which will inherit the transformation of the clipped object.
     * 
     * 用于裁剪的路径，所有 Group 内的路径在绘制时都会被这个路径裁剪，该路径会继承被裁减对象的变换。
     * 
     * @readonly
     * @see http://www.w3.org/TR/2dcontext/#clipping-region
     */

    this.clipPath = null; // FIXME Stateful must be mixined after style is setted
    // Stateful.call(this, options);

<span id='qrenderer-graphic-Element-method-calculateTextPosition'>    /**
</span>     * The String value of `textPosition` needs to be calculated to a real postion.
     * For example, `&#39;inside&#39;` is calculated to `[rect.width/2, rect.height/2]`
     * by default. See `contain/text_util.js#calculateTextPosition` for more details.
     * But some coutom shapes like &quot;pin&quot;, &quot;flag&quot; have center that is not exactly
     * `[width/2, height/2]`. So we provide this hook to customize the calculation
     * for those shapes. It will be called if the `style.textPosition` is a String.
     * @param {Obejct} [out] Prepared out object. If not provided, this method should
     *        be responsible for creating one.
     * @param {Style} style
     * @param {Object} rect {x, y, width, height}
     * @return {Obejct} out The same as the input out.
     *         {
     *             x: Number. mandatory.
     *             y: Number. mandatory.
     *             textAlign: String. optional. use style.textAlign by default.
     *             textVerticalAlign: String. optional. use style.textVerticalAlign by default.
     *         }
     */

    this.calculateTextPosition = null;
<span id='qrenderer-graphic-Element-property-invisible'>    /**
</span>     * @property {Boolean} invisible
     * Whether the displayable object is visible. when it is true, the displayable object
     * is not drawn, but the mouse event can still trigger the object.
     */

    this.invisible = false;
<span id='qrenderer-graphic-Element-property-z'>    /**
</span>     * @property {Number} z
     */

    this.z = 0;
<span id='qrenderer-graphic-Element-property-z2'>    /**
</span>     * @property {Number} z2
     */

    this.z2 = 0;
<span id='qrenderer-graphic-Element-property-qlevel'>    /**
</span>     * @property {Number} qlevel
     * The q level determines the displayable object can be drawn in which layer canvas.
     */

    this.qlevel = 0;
    this.transformable = true;
<span id='qrenderer-graphic-Element-property-hasTransformControls'>    /**
</span>     * @property {Boolean} hasTransformControls
     * Whether this object has transform controls now, hasTransformControls will be set to true when element is clicked.
     * 
     * 元素当前是否带有变换控制工具，当元素被点击的时候 hasTransformControls 会被设置为 true。
     */

    this.hasTransformControls = false;
<span id='qrenderer-graphic-Element-property-controls'>    /**
</span>     * @property {Array&lt;Control&gt;} controls
     * Whether show transform controls, if showTransformControls is false, no transform controls will be rendered.
     * 
     * 
     * 是否显示变换控制工具，如果此标志位被设置为 false，无论什么情况都不会显示变换控制器。
     */

    this.showTransformControls = false;
<span id='qrenderer-graphic-Element-property-transformControls'>    /**
</span>     * @property {Array&lt;Control&gt;} transformControls
     * Transform controls.
     * 
     * 
     * 变换控制工具。
     */

    this.transformControls = [];
<span id='qrenderer-graphic-Element-property-silent'>    /**
</span>     * @property {Boolean} silent
     * Whether to respond to mouse events.
     */

    this.silent = false;
<span id='qrenderer-graphic-Element-property-culling'>    /**
</span>     * @property {Boolean} culling
     * If enable culling
     */

    this.culling = false;
<span id='qrenderer-graphic-Element-property-cursor'>    /**
</span>     * @property {String} cursor
     * Mouse cursor when hovered
     */

    this.cursor = this.options.draggable ? &#39;move&#39; : &#39;default&#39;;
<span id='qrenderer-graphic-Element-property-rectHover'>    /**
</span>     * @property {String} rectHover
     * If hover area is bounding rect
     */

    this.rectHover = false;
<span id='qrenderer-graphic-Element-property-progressive'>    /**
</span>     * @property {Boolean} progressive
     * Render the element progressively when the value &gt;= 0,
     * usefull for large data.
     */

    this.progressive = false;
<span id='qrenderer-graphic-Element-property-incremental'>    /**
</span>     * @property {Boolean} incremental
     */

    this.incremental = false;
<span id='qrenderer-graphic-Element-property-globalScaleRatio'>    /**
</span>     * @property {Boolean} globalScaleRatio
     * Scale ratio for global scale.
     */

    this.globalScaleRatio = 1;
<span id='qrenderer-graphic-Element-property-animationProcessList'>    /**
</span>     * @property {Array} animationProcessList
     * All the AnimationProcesses on this Element.
     */

    this.animationProcessList = [];
<span id='qrenderer-graphic-Element-property-ctx'>    /**
</span>     * @property {CanvasRenderingContext2D} ctx
     * Cache canvas context, this will set by Painter.
     */

    this.ctx = null;
<span id='qrenderer-graphic-Element-property-prevEl'>    /**
</span>     * @property {Element} prevEl
     * Cache previous element, this will set by Painter.
     */

    this.prevEl = null;
    this.originalBoundingRect = null;
<span id='qrenderer-graphic-Element-property-__qr'>    /**
</span>     * @private
     * @property {QuarkRenderer} __qr
     * 
     * QuarkRenderer instance will be assigned when element is associated with qrenderer
     * 
     * QuarkRenderer 实例对象，会在 element 添加到 qrenderer 实例中后自动赋值
     */

    this.__qr = null;
<span id='qrenderer-graphic-Element-property-__dirty'>    /**
</span>     * @private
     * @property {Boolean} __dirty
     * 
     * Dirty flag. From which painter will determine if this displayable object needs to be repainted.
     * 
     * 这是一个非常重要的标志位，在绘制大量对象的时候，把 __dirty 标记为 false 可以节省大量操作。
     */

    this.__dirty = true;
<span id='qrenderer-graphic-Element-property-__clipPaths'>    /**
</span>     * @private
     * @property  __clipPaths
     * Shapes for cascade clipping.
     * Can only be `null`/`undefined` or an non-empty array, MUST NOT be an empty array.
     * because it is easy to only using null to check whether clipPaths changed.
     */

    this.__clipPaths = null;
<span id='qrenderer-graphic-Element-property-__boundingRect'>    /**
</span>     * @protected
     * @property __boundingRect 边界矩形
     */

    this.__boundingRect = null;
<span id='qrenderer-graphic-Element-property-style'>    /**
</span>     * @property {Style} style
     */

    this.style = new Style(this.options.style, this);
<span id='qrenderer-graphic-Element-property-shape'>    /**
</span>     * @property {Object} shape 形状
     */

    this.shape = {}; // Extend default shape

    var defaultShape = this.options.shape;

    if (defaultShape) {
      for (var name in defaultShape) {
        if (!this.shape.hasOwnProperty(name) &amp;&amp; defaultShape.hasOwnProperty(name)) {
          this.shape[name] = defaultShape[name];
        }
      }
    }

    classUtil.inheritProperties(this, Eventful, this.options);
    classUtil.inheritProperties(this, Animatable, this.options);
    classUtil.inheritProperties(this, Draggable, this.options);
    classUtil.inheritProperties(this, Transformable, this.options);
    classUtil.copyOwnProperties(this, this.options, [&#39;style&#39;, &#39;shape&#39;]);
    this.on(&quot;addToStorage&quot;, this.addToStorageHandler, this);
    this.on(&quot;delFromStorage&quot;, this.delFromStorageHandler, this);
    this.one(&quot;afterRender&quot;, function () {
      _this.originalBoundingRect = _this.getBoundingRect();
    }, this);
  }
<span id='qrenderer-graphic-Element-method-hide'>  /**
</span>   * @method hide
   * 
   * Hide the element.
   * 
   * 隐藏元素。
   */


  _createClass(Element, [{
    key: &quot;hide&quot;,
    value: function hide() {
      this.ignore = true;
      this.__qr &amp;&amp; this.__qr.dirty();
    }
<span id='qrenderer-graphic-Element-method-show'>    /**
</span>     * @method show
     * 
     * Show the element.
     * 
     * 显示元素。
     */

  }, {
    key: &quot;show&quot;,
    value: function show() {
      this.ignore = false;
      this.__qr &amp;&amp; this.__qr.dirty();
    }
<span id='qrenderer-graphic-Element-method-setClipPath'>    /**
</span>     * @method setClipPath
     * 
     * Set clip path dynamicly.
     * 
     * 动态设置剪裁路径。
     * 
     * @param {Path} clipPath
     */

  }, {
    key: &quot;setClipPath&quot;,
    value: function setClipPath(clipPath) {
      // Remove previous clip path
      if (this.clipPath &amp;&amp; this.clipPath !== clipPath) {
        this.removeClipPath();
      }

      this.clipPath = clipPath;
      clipPath.__qr = this.__qr;
      clipPath.__clipTarget = this;
      clipPath.trigger(&quot;addToStorage&quot;, this.__storage); // trigger addToStorage manually
      //TODO: FIX this，子类 Path 中的 dirty() 方法有参数。

      this.dirty();
    }
<span id='qrenderer-graphic-Element-method-removeClipPath'>    /**
</span>     * @method removeClipPath
     * 
     * Remove clip path dynamicly.
     * 
     * 动态删除剪裁路径。
     */

  }, {
    key: &quot;removeClipPath&quot;,
    value: function removeClipPath() {
      if (this.clipPath) {
        this.clipPath.__qr = null;
        this.clipPath.__clipTarget = null;
        this.clipPath &amp;&amp; this.clipPath.trigger(&quot;delFromStorage&quot;, this.__storage);
        this.clipPath = null;
      }
    }
<span id='qrenderer-graphic-Element-method-dirty'>    /**
</span>     * @protected
     * @method dirty
     * 
     * Mark displayable element dirty and refresh next frame.
     * 
     * 把元素标记成脏的，在下一帧中刷新。
     */

  }, {
    key: &quot;dirty&quot;,
    value: function dirty() {
      this.__dirty = this.__dirtyText = true;
      this.__boundingRect = null;
      this.__qr &amp;&amp; this.__qr.dirty();
    }
<span id='qrenderer-graphic-Element-method-addToStorageHandler'>    /**
</span>     * @method addToStorageHandler
     * Add self to qrenderer instance.
     * Not recursively because it will be invoked when element added to storage.
     * 
     * 把当前对象添加到 qrenderer 实例中去。
     * 不会递归添加，因为当元素被添加到 storage 中的时候会执行递归操作。
     * @param {qrenderer.core.Storage} storage
     */

  }, {
    key: &quot;addToStorageHandler&quot;,
    value: function addToStorageHandler(storage) {
      this.__storage = storage;
      this.__qr &amp;&amp; this.__qr.globalAnimationMgr.addAnimatable(this);
      this.clipPath &amp;&amp; this.clipPath.trigger(&quot;addToStorage&quot;, this.__storage);
      this.dirty();
    }
<span id='qrenderer-graphic-Element-method-delFromStorageHandler'>    /**
</span>     * @method delFromStorageHandler
     * Remove self from qrenderer instance.
     * 
     * 把当前对象从 qrenderer 实例中删除。
     * @param {qrenderer.core.Storage} storage
     */

  }, {
    key: &quot;delFromStorageHandler&quot;,
    value: function delFromStorageHandler(storage) {
      this.animationProcessList.forEach(function (item, index) {
        item.trigger(&quot;stop&quot;);
      });
      this.animationProcessList = [];
      this.clipPath &amp;&amp; this.clipPath.trigger(&quot;delFromStorage&quot;, this.__storage);
      this.__qr = null;
      this.__storage = null;
      this.dirty();
    }
<span id='qrenderer-graphic-Element-method-render'>    /**
</span>     * @protected
     * @method render
     * Callback during render.
     */

  }, {
    key: &quot;render&quot;,
    value: function render() {
      var ctx = this.ctx;
      var prevEl = this.prevEl;

      if (this.showTransformControls &amp;&amp; this.hasTransformControls) {
        this.renderTransformControls();
      } //FIXME:refactor the render system: element self -&gt; text -&gt; transform controls -&gt; link controls
      // Draw rect text


      if (this.style.text) {
        // Only restore transform when needs draw text.
        this.restoreTransform(ctx);
        this.drawRectText(ctx, this.getBoundingRect());
        this.applyTransform(ctx);
      }
    }
  }, {
    key: &quot;renderTransformControls&quot;,
    value: function renderTransformControls() {
      var _this2 = this;

      var ctx = this.ctx;
      var prevEl = this.prevEl; //draw transform controls

      this.transformControls = [];
      var positions = [&#39;TL&#39;, &#39;T&#39;, &#39;TR&#39;, &#39;R&#39;, &#39;BR&#39;, &#39;B&#39;, &#39;BL&#39;, &#39;L&#39;, &#39;SPIN&#39;];
      positions.forEach(function (p, index) {
        var control = new Control({
          el: _this2,
          name: p
        }).render();

        _this2.transformControls.push(control);
      }); //draw bounding rect

      var control0 = this.transformControls[0];
      var control4 = this.transformControls[4];
      var p1 = [control0.x3 - control0.width / 2, control0.y3 - control0.height / 2];
      var p2 = [control4.x1 + control4.width / 2, control4.y1 + control4.height / 2];
      var w = p2[0] - p1[0];
      var h = p2[1] - p1[1];
      ctx.save();
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.lineWidth = control0.lineWidth;
      ctx.fillStyle = control0.fillStyle;
      ctx.strokeStyle = control0.strokeStyle;
      ctx.translate(control0.translate[0], control0.translate[1]);
      ctx.rotate(-control0.rotation);
      ctx.strokeRect(p1[0], p1[1], w, h);
      ctx.closePath(); //draw connet line

      var x1 = 0,
          y1 = 0,
          x2 = 0,
          y2 = 0;
      x1 = this.transformControls[1].x1 + this.transformControls[1].width / 2;
      y1 = this.transformControls[1].y1;
      x2 = this.transformControls[8].x1 + this.transformControls[8].width / 2;
      y2 = this.transformControls[8].y1 + this.transformControls[8].height;
      ctx.beginPath();
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
      ctx.stroke();
      ctx.restore();
    }
<span id='qrenderer-graphic-Element-method-getBoundingRect'>    /**
</span>     * @method getBoundingRect
     * Get bounding rect of this element, NOTE: 
     * this method will return the bounding rect without transforming(translate/scale/rotate/skew). 
     * However, direct modifications to the shape property will be reflected in the bouding-rect.
     * For example,  if we modify this.shape.width directly, then the new width property will be calculated.
     * 
     * 
     * 获取当前元素的边界矩形，注意：
     * 此方法返回的是没有经过 transform(translate/scale/rotate/skew) 处理的边界矩形，但是对 shape 属性直接进行的修改会反映在获取的边界矩形上。
     * 例如，用代码直接对 this.shape.width 进行赋值，那么在计算边界矩形时就会用新的 width 属性进行计算。
     */

  }, {
    key: &quot;getBoundingRect&quot;,
    value: function getBoundingRect() {} //All subclasses should provide implementation for this method. 
    //所有子类都需要提供此方法的具体实现。

<span id='qrenderer-graphic-Element-method-containPoint'>    /**
</span>     * @protected
     * @method containPoint
     * 
     * If displayable element contain coord x, y, this is an util function for
     * determine where two elements overlap.
     * 
     * 图元是否包含坐标(x,y)，此工具方法用来判断两个图元是否重叠。
     * 
     * @param  {Number} x
     * @param  {Number} y
     * @return {Boolean}
     */

  }, {
    key: &quot;containPoint&quot;,
    value: function containPoint(x, y) {
      return this.rectContainPoint(x, y);
    }
<span id='qrenderer-graphic-Element-method-rectContainPoint'>    /**
</span>     * @protected
     * @method rectContainPoint
     * 
     * If bounding rect of element contain coord x, y.
     * 
     * 用来判断当前图元的外框矩形是否包含坐标点(x,y)。
     * 
     * @param  {Number} x
     * @param  {Number} y
     * @return {Boolean}
     */

  }, {
    key: &quot;rectContainPoint&quot;,
    value: function rectContainPoint(x, y) {
      var coord = this.globalToLocal(x, y);
      var rect = this.getBoundingRect();
      return rect.containPoint(coord[0], coord[1]);
    }
<span id='qrenderer-graphic-Element-method-traverse'>    /**
</span>     * @method traverse
     * @param  {Function} cb
     * @param  {Object}  context
     */

  }, {
    key: &quot;traverse&quot;,
    value: function traverse(cb, context) {
      cb.call(context, this);
    }
<span id='qrenderer-graphic-Element-method-_attrKV'>    /**
</span>     * @protected
     * @method _attrKV
     * @param {String} key
     * @param {Object} value
     */

  }, {
    key: &quot;_attrKV&quot;,
    value: function _attrKV(key, value) {
      if (key === &#39;style&#39;) {
        classUtil.copyOwnProperties(this.style, value);
      } else if (key === &#39;position&#39; || key === &#39;scale&#39; || key === &#39;origin&#39; || key === &#39;skew&#39; || key === &#39;translate&#39;) {
        var target = this[key] ? this[key] : [];
        target[0] = value[0];
        target[1] = value[1];
      } else {
        this[key] = value;
      }
    }
<span id='qrenderer-graphic-Element-method-attr'>    /**
</span>     * @method attr
     * 
     * Modify attribute, this method will mark current object as dirty.
     * 
     * 修改对象上的属性，使用此方法修改对象上的属性会导致对象被标记成 dirty。
     * 
     * @param {String|Object} key
     * @param {*} value
     */

  }, {
    key: &quot;attr&quot;,
    value: function attr(key, value) {
      if (dataUtil.isString(key)) {
        this._attrKV(key, value);
      } else if (dataUtil.isObject(key)) {
        for (var name in key) {
          if (key.hasOwnProperty(name)) {
            this._attrKV(name, key[name]);
          }
        }
      }

      this.dirty();
      return this;
    }
<span id='qrenderer-graphic-Element-method-toJSONObject'>    /**
</span>     * @method toJSONObject
     * The subclass of Element can provide its own implementation.
     * 
     * 
     * Element 的子类可以覆盖此方法提供自己的实现。
     */

  }, {
    key: &quot;toJSONObject&quot;,
    value: function toJSONObject() {
      var result = {
        id: this.id,
        name: this.name,
        type: this.type,
        ignore: this.ignore,
        invisible: this.invisible,
        draggable: this.draggable,
        transformable: this.transformable,
        hasTransformControls: this.hasTransformControls,
        showTransformControls: this.showTransformControls,
        position: this.position,
        shape: this.shape,
        style: this.style
      };
      return result;
    }
  }]);

  return Element;
}();

classUtil.mixin(Element, Eventful);
classUtil.mixin(Element, Animatable);
classUtil.mixin(Element, Draggable);
classUtil.mixin(Element, Transformable);
classUtil.mixin(Element, RectText);
var _default = Element;
module.exports = _default;</pre>
</body>
</html>
